package network

import (
	"context"
	"crypto/rand"
	"fmt"
	block "github.com/corgi-kx/blockchain_golang/blc"
	log "github.com/corgi-kx/logcustom"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/peer"
	"github.com/libp2p/go-libp2p-core/protocol"
	"github.com/libp2p/go-libp2p-core/routing"
	kaddht "github.com/libp2p/go-libp2p-kad-dht"
	mplex "github.com/libp2p/go-libp2p-mplex"
	pubsub "github.com/libp2p/go-libp2p-pubsub"
	secio "github.com/libp2p/go-libp2p-secio"
	yamux "github.com/libp2p/go-libp2p-yamux"
	"github.com/libp2p/go-libp2p/p2p/discovery"
	"github.com/libp2p/go-tcp-transport"
	"github.com/multiformats/go-multiaddr"
	"math/big"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

//在P2P网络中已发现的节点池
//key:节点ID  value:节点详细信息
var peerPool = make(map[string]peer.AddrInfo)
var ctx = context.Background()
var send = Send{}

//Websocket推送
var wsend = WebsocketSend{}

//gossip网络
var gossip = pubsub.PubSub{}

//启动本地节点
func StartNode(clier Clier) {
	//先获取本地区块最新高度
	bc := block.NewBlockchain()
	block.NewestBlockHeight = bc.GetLastBlockHeight()
	block.DifficultyInt = big.NewInt(1).SetBytes(bc.GetLastBlockDifficulty())
	log.Infof("[*] 监听IP地址: %s 端口号: %s", ListenHost, ListenPort)

	//传输层接口 TCP
	transports := libp2p.ChainOptions(
		libp2p.Transport(tcp.NewTCPTransport),
	)
	//多路复用
	muxers := libp2p.ChainOptions(
		libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport),
		libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport),
	)

	//安全传输
	security := libp2p.Security(secio.ID, secio.New)

	//监听
	// 创建本地节点地址信息
	sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s", ListenHost, ListenPort))
	listenAddrs := libp2p.ListenAddrs(sourceMultiAddr)

	//Kademila路由
	var dht *kaddht.IpfsDHT
	newDHT := func(h host.Host) (routing.PeerRouting, error) {
		var err error
		dht, err = kaddht.New(ctx, h)
		return dht, err
	}
	routing_ := libp2p.Routing(newDHT)

	r := rand.Reader
	// 为本地节点创建RSA密钥对
	prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
	if err != nil {
		log.Panic(err)
	}

	//传入地址信息，RSA密钥对信息，生成libp2p本地host信息
	localhost, err := libp2p.New(
		ctx,
		transports,
		//监听地址
		listenAddrs,
		libp2p.Identity(prvKey),
		muxers,
		security,
		routing_,
		libp2p.NATPortMap(),
		libp2p.DefaultEnableRelay,
		libp2p.DefaultPeerstore,
	)
	if err != nil {
		log.Panic(err)
	}

	//写入全局变量本地主机信息
	localHost = localhost
	//写入全局变量本地P2P节点地址详细信息
	localAddr = fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", ListenHost, ListenPort, localhost.ID().Pretty())
	log.Infof("[*] 你的P2P地址信息: %s", localAddr)

	//启动监听本地端口，并且传入一个处理流的函数，当本地节点接收到流的时候回调处理流的函数
	localhost.SetStreamHandler(protocol.ID(ProtocolID), handleStream)

	//启用gossip网路在p2p之间广播信息
	ps, err := pubsub.NewGossipSub(ctx, localhost)
	if err != nil {
		panic(err)
	}
	gossip = *ps
	sub, err := ps.Subscribe(PubsubTopic)
	if err != nil {
		panic(err)
	}
	go pubsubHandler(ctx, sub)

	fmt.Printf("addr: %s\n", localhost.ID())
	for _, addr := range localhost.Addrs() {
		fmt.Println("Listening on", addr)
	}

	//连接bootstrap节点
	targetAddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%s/p2p/%s", BootstrapHost, BootstrapPort, BootstrapAddr))
	if err != nil {
		panic(err)
	}

	targetInfo, err := peer.AddrInfoFromP2pAddr(targetAddr)
	if err != nil {
		panic(err)
	}

	err = localhost.Connect(ctx, *targetInfo)
	for err != nil {
		fmt.Println(err)
		fmt.Println("尝试重新连接bootstrap节点......")
		err = localhost.Connect(ctx, *targetInfo)
		time.Sleep(3 * time.Second)
	}

	fmt.Println("Connected to", targetInfo.ID)

	//局域网络节点发现
	//go findP2PPeer()
	mdns, err := discovery.NewMdnsService(ctx, localhost, time.Second*10, RendezvousString)
	if err != nil {
		panic(err)
	}
	mdns.RegisterNotifee(&mdnsNotifee{h: localhost, ctx: ctx})

	//路由bootstrap
	err = dht.Bootstrap(ctx)
	if err != nil {
		panic(err)
	}

	//用于节点发现的时间间隔
	time.Sleep(1 * time.Second)

	//启一个go程去向其他p2p节点发送高度信息，来进行更新区块数据
	go sendVersionToPeers()

	//启动一个go程负责区块打包
	go mineBlock()

	fmt.Println("本地网络节点已启动,详细信息请查看log日志!")
	signalHandle()
}

//向其他p2p节点发送高度信息，来进行更新区块数据
func sendVersionToPeers() {
	send.SendVersionToPeers(block.NewestBlockHeight)
}

//节点退出信号处理
func signalHandle() {
	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
	<-sigs
	send.SendSignOutToPeers()
	fmt.Println("本地节点已退出")
	time.Sleep(time.Second)
	os.Exit(0)
}

//websocket服务
func StartWebsocketServer(clier Clier) {
	fmt.Println("Listening on", "http://"+WebsocketAddr+":"+WebsocketPort+"/ws")
	log.Info("=====Starting v3 websocketServer:" + WebsocketAddr + ":" + WebsocketPort + "=======")
	http.HandleFunc("/ws", wsHandler)
	http.HandleFunc("/post_transactions", httpGenerateTransactions)
	http.HandleFunc("/find_utxo_from_address", httpFindUTXOFromAddress)
	http.HandleFunc("/find_transaction", httpFindTransaction)
	http.HandleFunc("/push_mined_blockheader", httpPushMinedBlockHeader)
	http.HandleFunc("/get_balance", httpGetBalance)
	http.HandleFunc("/get_block", httpGetBlock)
	_ = http.ListenAndServe(WebsocketAddr+":"+WebsocketPort, nil)
}

type mdnsNotifee struct {
	h   host.Host
	ctx context.Context
}

func (m *mdnsNotifee) HandlePeerFound(pi peer.AddrInfo) {
	_ = m.h.Connect(m.ctx, pi)
}
